#include <iostream>
#include <sys/select.h>
#include "Sock.hpp"

using namespace std;

// fd_set fds;//文件描述符集的位图比特位数是1024（平台）同时能监管的fd是有限的
#define NUM sizeof(fd_set) * 8
#define FD_NONE -1

// 只负责读取，写入和异常不做处理（epoll）
class SelectServer
{
public:
    SelectServer(const uint16_t port = 8080) : _port(port)
    {
        _listensock = Sock::Socket();
        Sock::Bind(_listensock, port);
        Sock::Listen(_listensock);
        logMessage(DEBUG, "%s", "init SelectServer success\n");
        for (int i = 0; i < NUM; i++)
            _fd_array[i] = FD_NONE;
        _fd_array[0] = _listensock; // 规定0位置为监听套接字
    }
    void Start()
    {
        while (true)
        {
            struct timeval timeout = {3, 0}; // 超时策略

            fd_set rfds;             // 读位图
            FD_ZERO(&rfds);          // 清空
            int maxfd = _listensock; // 遍历获取最大的fd，用来设置nfds
            for (int i = 0; i < NUM; i++)
            {
                if (_fd_array[i] != FD_NONE) // 合法fd
                {
                    if (maxfd < _fd_array[i])
                        maxfd = _fd_array[i];

                    FD_SET(_fd_array[i], &rfds); // 设置进rfds
                }
            }

            // accept获取连接：本质也是一种IO，将其交给select去等待,就绪后再去获取连接
            // int n = select(_listensock + 1, &rfds, NULL, NULL, &timeout); // select超时时间内等待
            int n = select(maxfd + 1, &rfds, NULL, NULL, NULL); // select阻塞等待

            switch (n)
            {
            case 0: // 超时
                logMessage(DEBUG, "%s\n", "timeout");
                break;
            case -1: // select出错
                logMessage(WARNING, "%s\n", "select error");
                break;
            default: // 获取到新连接/新请求
                handleEvent(rfds);
                break;
            }
        }
    }
    ~SelectServer()
    {
        if (_listensock >= 0)
            close(_listensock);
    }
private:
    void handleEvent(const fd_set &rfds) // 该文件集是select等待成功的结果：表示某些fd的读事件就绪
    {
        // 1.获取新连接
        if (FD_ISSET(_listensock, &rfds)) // 获取到新连接
        {
            Accept();
        }

        // 2.服务新请求
        for (int i = 1; i < NUM; i++)
        {
            if (_fd_array[i] == FD_NONE || (!FD_ISSET(_fd_array[i], &rfds)))
                continue; // 如果不是合法fd，或者该合法fd没有就绪事件，就跳过
            Recver(i);
        }
    }
    void Accept()
    {
        string cli_ip;
        uint16_t cli_port;
        int sock = Sock::Accept(_listensock, &cli_ip, &cli_port); // 此时获取连接，就不会阻塞了，因为select已经等待过了
        logMessage(DEBUG, "get new connect[%s:%d]:%d", cli_ip.c_str(), cli_port, sock);

        // 单进程 + select：实现单进程同时为多个连接服务
        // 获取到的连接也需要交给select去等待新请求
        // 否则由进程自己等待，就会阻塞等待，服务器无法继续获取新连接/请求
        // 添加sock到数组内，下次循环自动添加等待
        int pos = 1;
        for (; pos < NUM; pos++)
        {
            if (_fd_array[pos] == FD_NONE)
                break;
        }
        if (pos == NUM) // 已经超过了能等待的fd数量
        {
            logMessage(ERROR, "select fd_array already full! close:%d", sock);
            close(sock);
        }
        else
        {
            _fd_array[pos] = sock;
        }
    }
    void Recver(int i)
    {
        // 此时进行读取，必然不会阻塞
        int sock = _fd_array[i];
        char buffer[1024];
        ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
        // 这样读取，不能保证读到一个完整报文，需要定制应用层协议
        if (s > 0)
        {
            buffer[s] = 0;
            cout << sock << ":" << buffer;
        }
        else if (s == 0) // 客户端关闭,断开连接:也是一种IO，也会被select检测到
        {
            // 关闭sock
            close(sock);
            // 从fd数组中去掉这个sock
            _fd_array[i] = FD_NONE;
            logMessage(NORMAL, "client[%d] quit\n", sock);
        }
        else // 出错
        {
            logMessage(WARNING, "recv error\n");
        }
    }

private:
    uint16_t _port;
    int _listensock;
    int _fd_array[NUM];
};